
import apcslib.Format;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.audio.AudioNode;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.PhysicsCollisionEvent;
import com.jme3.bullet.collision.PhysicsCollisionListener;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Sphere.TextureMode;
import com.jme3.texture.Texture;
import de.lessvoid.nifty.controls.TextField;
import de.lessvoid.nifty.elements.render.TextRenderer;

/**
 *
 * @author David Zhang
 */
public class Game extends SimpleApplication implements PhysicsCollisionListener, ActionListener{
    AudioNode audio_gun;
    boolean mouseGrabbed = true, left = false, right = false, forward = false, back = false;
    BulletAppState bas;
    ClientMain client;
    Enemy[] enemy = new Enemy[10];
    Gui gui;
    int bulletnum;
    Node playerNode, bullets, enemies;
    Player character;
    RigidBodyControl[] ball_phy = new RigidBodyControl[60];
    String name;
    Vector3f walkDirection = new Vector3f();

    @Override
    public void simpleInitApp() {
        client = new ClientMain();
        guiNode.detachAllChildren();
        gui = new Gui(guiFont);
    }
    
    @Override
    public void simpleUpdate(float tpf){
        if(gui.loaded == 1){
            gui.loadStep(); //Calls the loading method
        }
        else if (gui.loaded == 2){
            updateGUI();
            movePlayer();
            Vector3f characterloc = character.getControl().getPhysicsLocation();
            Vector3f characterdir = character.getControl().getViewDirection();
            sendMovement(characterloc.x + "", characterloc.y + "", characterloc.z + "");
            sendDirection(characterdir.x + "", characterdir.y + "", characterdir.z + "");
            clearBullets();
            character.checkDeath();
        }
    }
    
    public void clearBullets(){
        if(bulletnum >= 10){
            bullets.detachAllChildren();
            for(int i = 0; i < bulletnum; i++){
                if(ball_phy[bulletnum] != null)
                    ball_phy[bulletnum] = null;
            }
            bulletnum = 0;
       }
    }

    public void collision(PhysicsCollisionEvent pce) {
        if(pce.getNodeA().getName().equals("bullet"))bullets.detachChild(pce.getNodeA());
        else if(pce.getNodeB().getName().equals("bullet"))bullets.detachChild(pce.getNodeB());
        if(pce.getNodeA().getName().equals("bullet") && pce.getNodeB().getName().substring(0, 5).equals("enemy")){
                int i = Integer.parseInt(pce.getNodeB().getName().substring(5));
                bullets.detachChild(pce.getNodeA());
                enemyCollision(i);
        }
        else if(pce.getNodeA().getName().substring(0, 5).equals("enemy") && pce.getNodeB().getName().equals("bullet")){
                int i = Integer.parseInt(pce.getNodeA().getName().substring(5));
                bullets.detachChild(pce.getNodeB());
                enemyCollision(i);
        }
    }
    
    public void enemyCollision(int i){
        enemy[i].changeHealth(-(character.getGun().getDamage()));
        enemy[i].checkDeath(character);
    }

    public void initAudio() {
        /* gun shot sound is to be triggered by a mouse click. */
        audio_gun = new AudioNode(assetManager, "Sounds/Effects/Gun.wav", false);
        audio_gun.setLooping(false);
        audio_gun.setVolume(2);
        rootNode.attachChild(audio_gun);
    }

    public void initCannonBall() {
        Material stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); //Creates the cannon ball material
        TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG"); //Creates the bullet texture key
        key2.setGenerateMips(true);
        Texture tex2 = assetManager.loadTexture(key2); //Loads the bullet texture
        stone_mat.setTexture("ColorMap", tex2); //Sets the texture of the material
    }
    
    public void makeCannonBall(){
        Sphere sphere = new Sphere(32, 32, 0.4f, true, false);
        sphere.setTextureMode(TextureMode.Projected);
        Geometry ball_geo = new Geometry("cannon ball", sphere); //Creates the geometry of the bullet
        ball_geo.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")); //Sets the material of the geometry
        bullets.attachChild(ball_geo); //Attachs the geometry to the bullets node
        bullets.setName("bullet"); //Sets the name of the bullet node
        ball_geo.setName("bullet"); //Sets the name of the bullet geometry
        ball_geo.setLocalTranslation(cam.getLocation().add(cam.getDirection().add(cam.getDirection()))); //Sets the location of the bullet
        ball_phy[bulletnum] = new RigidBodyControl(1f); //Creates the bullet physics with mass of 1
        ball_geo.addControl(ball_phy[bulletnum]); //Makes the physics control the geometry
        bas.getPhysicsSpace().add(ball_phy[bulletnum]); //Adds the bullet physics to the physics space
        ball_phy[bulletnum].setLinearVelocity(cam.getDirection().mult(300)); //Accelerates the ball
        bulletnum++; //Increments the bullet physics array control variable
    }
    
    public void movePlayer(){
        Vector3f camDir = cam.getDirection().clone().multLocal(0.6f); //
        Vector3f camLeft = cam.getLeft().clone().multLocal(0.4f);
        camDir.y = 0;
        camLeft.y = 0;
        walkDirection.set(0, 0, 0);
        if (left) { walkDirection.addLocal(camLeft);}
        if (right) { walkDirection.addLocal(camLeft.negate()); }
        if (forward)    { walkDirection.addLocal(camDir); }
        if (back)  { walkDirection.addLocal(camDir.negate()); }
        character.getControl().setWalkDirection(walkDirection);
        cam.setLocation(character.getControl().getPhysicsLocation());
        character.getControl().setViewDirection(cam.getDirection());
    }

    public void onAction(String input, boolean value, float tpf) {
        if(gui.loaded == 2 && mouseGrabbed){
            if(input.equals("Left"))left = value; //Sets left to the boolean value(Used later)
            else if(input.equals("Right"))right = value; //Sets right to the boolean value(Used later)
            else if(input.equals("Forward"))forward = value; //Sets forward to the boolean value(Used later)
            else if(input.equals("Back"))back = value; //Sets back to the boolean value(Used later)
            else if(input.equals("Jump"))character.getControl().jump(); //Makes the character jump
            else if(input.equals("reload"))character.getGun().reload(); //Makes the character's gun reload
            else if (input.equals("shoot") && !value && mouseGrabbed && !(character.getGun().getName().equals("Pick"))) {
                character.getGun().shoot();
                if(!(character.getGun().getReloading()) && character.getGun().getBullets() > 0){
                    makeCannonBall();
                }//Creates a bullet 
            }
            else if (input.equals("pick") && !value && !(character.getGun().getGunName().equals("Pick"))){
                if(mouseGrabbed){
                    character.setGun("Pick"); //Sets gun to false
                    gui.nifty.getCurrentScreen().findElementByName("chs").getRenderer(TextRenderer.class).setText("[]");
                }
            }
            else if (input.equals("gun") && !value && !(character.getGun().getGunName().equals("Pistol"))){
                if(mouseGrabbed){
                character.setGun("Pistol"); //Sets gun to true
                gui.nifty.getCurrentScreen().findElementByName("chs").getRenderer(TextRenderer.class).setText("+");
                }
            }
            else if(input.equals("quit")){
                ChatMessage leave = new ChatMessage("leave", client.myIndex+"");
                leave.setReliable(true);
                client.client.send(leave);
                client.send(name + ": has exited the game");
                client.client.close();
                System.exit(0);
            }
        }
        else if(!mouseGrabbed){
            if(input.equals("send") && !mouseGrabbed && !value){
                if(gui.loaded != 0){
                    client.send(name + ": " + gui.nifty.getCurrentScreen().findNiftyControl("input", TextField.class).getText());
                    gui.nifty.getCurrentScreen().findNiftyControl("input", TextField.class).setText("");
                }
            }
        }
        if(input.equals("release") && !value){
            if(!mouseGrabbed){
                //inputManager.setCursorVisible(false); //If the cursor is visible it makes it invisible
                mouseGrabbed = true; //Sets the boolean to true
                gui.nifty.getScreen("end").findNiftyControl("input", TextField.class).disable();
            }
            else{
                //inputManager.setCursorVisible(true); //If the cursor is not visible it makes it visible
                mouseGrabbed = false; //Sets the boolean to false
                gui.nifty.getScreen("end").findNiftyControl("input", TextField.class).enable();
                gui.nifty.getScreen("end").findNiftyControl("input", TextField.class).setFocus();
            }
        }
    }
    
    public void sendMovement(String x, String y, String z){
        if(client.myIndex != -1){
            ChatMessage coors = new ChatMessage("coordinates", client.myIndex + ":x" + x +"y" + y +"z"+ z);
            coors.setReliable(true);
            client.client.send(coors);
        }
    }
    
    public void sendDirection(String x, String y, String z){
        if(client.myIndex != -1){
            ChatMessage coors = new ChatMessage("direction", client.myIndex + ":x" + x +"y" + y +"z"+ z);
            coors.setReliable(true);
            client.client.send(coors);
        }
    }
    
    public void setUpKeys(){
        inputManager.addMapping("send", new KeyTrigger(KeyInput.KEY_RETURN));
        inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A)); //Adds a mapping for 'A'
        inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D)); //Adds a mapping for 'D'
        inputManager.addMapping("Forward", new KeyTrigger(KeyInput.KEY_W)); //Adds a mapping for 'W'
        inputManager.addMapping("Back", new KeyTrigger(KeyInput.KEY_S)); //Adds a mapping for 'S'
        inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE)); //Adds a mapping for 'SPACE'
        inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); //Adds a mapping for left mouse button
        inputManager.addMapping("pick", new KeyTrigger(KeyInput.KEY_1)); //Adds a mapping for '1'
        inputManager.addMapping("gun", new KeyTrigger(KeyInput.KEY_2)); //Adds a mapping for '2'
        inputManager.addMapping("quit", new KeyTrigger(KeyInput.KEY_ESCAPE)); //Adds a mapping for 'ESC'
        inputManager.addMapping("reload", new KeyTrigger(KeyInput.KEY_R)); //Adds a mapping for 'R'
        inputManager.addMapping("release", new KeyTrigger(KeyInput.KEY_LCONTROL)); //Adds a mapping for 'P'
        inputManager.addListener(this, "Left"); //Adds a listener to the mapping "Left"
        inputManager.addListener(this, "Right"); //Adds a listener to the mapping "Right"
        inputManager.addListener(this, "Forward"); //Adds a listener to the mapping "Forward"
        inputManager.addListener(this, "Back"); //Adds a listener to the mapping "Back"
        inputManager.addListener(this, "Jump"); //Adds a listener to the mapping "Jump"
        inputManager.addListener(this, "shoot"); //Adds a listener to the mapping "shoot"
        inputManager.addListener(this, "pick"); //Adds a listener to the mapping "pick"
        inputManager.addListener(this, "gun"); //Adds a listener to the mapping "gun"
        inputManager.addListener(this, "quit"); //Adds a listener to the mapping "quit"
        inputManager.addListener(this, "reload"); //Adds a listener to the mapping "reload"
        inputManager.addListener(this, "release"); //Adds a listener to the mappint "release"
        inputManager.addListener(this, "send");
    }

    public void setUpEnemy() {
        for(int i = 0; i < 10; i++){
            enemy[i] = new Enemy(4000, rootNode, 
                    assetManager.loadModel("Models/EnemyRobot/enemy.mesh.xml"), 
                    new Vector3f(0,10f,10f), i, character); //Constructs an enemy object
            enemy[i].add(bas); //Adds the enemy physics to the physics space
        }
    }

    public void setUpLight() {
        AmbientLight al = new AmbientLight(); //Constructs the ambient light object
        al.setColor(ColorRGBA.White.mult(1.3f)); //Sets the color of the light
        rootNode.addLight(al); //Adds the light to the root node
        DirectionalLight dl = new DirectionalLight(); //Constructs the directional light object
        dl.setColor(ColorRGBA.White); //Sets the color of the light
        dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal()); //Sets the direction of the light
        rootNode.addLight(dl); //Adds the light to the root node
        DirectionalLight dl2 = new DirectionalLight(); //Constructs the directional light object
        dl2.setColor(ColorRGBA.White); //Sets the color of the light
        dl2.setDirection(new Vector3f(-2.8f, 2.8f, 2.8f).normalizeLocal()); //Sets the direction of the light
        rootNode.addLight(dl2); //Adds the light to the root node
    }

    public void setUpPlayer() {
        character = new Player(assetManager.loadModel("Models/Oto/Oto.mesh.xml"), playerNode); //Constructs the Player object
        character.add(bas); //Adds the player physics to the physics space
    }

    public void setUpScene() {
        Spatial myModel = assetManager.loadModel("Scenes/TestSpace/test.scene");
        myModel.setLocalScale(30f);
        CollisionShape sceneShape = CollisionShapeFactory.createMeshShape((Node)myModel);
        RigidBodyControl myLandscape = new RigidBodyControl(sceneShape, 0);
        myModel.addControl(myLandscape);
        bas.getPhysicsSpace().add(myLandscape);
        rootNode.attachChild(myModel); //Adds the scene model to the root node
    }
    
    private void updateGUI(){
        gui.nifty.getScreen("end").findElementByName("moneys").getRenderer(TextRenderer.class).setText(
                " Moneys: "+ Format.left(character.getMoney(), 2, 2));
        gui.nifty.getScreen("end").findElementByName("clips").getRenderer(TextRenderer.class).setText(
                "Ammunition: "+ character.getGun().getBullets() + "/" + character.getGun().getClips());
        gui.nifty.getScreen("end").findElementByName("health").getRenderer(TextRenderer.class).setText(
                "" + character.getHealth());
    }
}